﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NumSharp;
using Newtonsoft.Json;
using DataType;
using System.IO;
using System.Diagnostics;
using DataType.CustomEnumType;

namespace MapCalclator
{
    public class Map
    {
        private int maxRow;// 最大行
        private int maxCol;// 最大列
        private int elevGrade;// 高程
        private int width;  // 六角格的半径，默认200m
        private Hex[,] hexes;// 保存了一个六角格的全部信息
        private List<List<List<Dictionary<int, float>>>> cost;// 通行代价
        /* 第一维：[0-车辆机动, 1-车辆行军, 2-步兵机动, 3-空中机动]
        [
            [
                [
                    { "能通行的邻域坐标 int " : "通行代价 float" }
                ]
            ]
        ]
        */
        public NDArray see;// 通视数据
        /* 五维数组，第一维(旧版)[0]：地对地、[1]：空对空、[2]：地对空，第一维新版有10种取值
        [
            [
                [
                    [
                        [
                            can_see: boolean
                        ]
                    ]
                ]
            ]
        ]
        */

        public int MaxRow { get => maxRow; }

        public int MaxCol { get => maxCol; }

        public int ElevGrade { get => elevGrade; }

        public int Width { get => width; }

        public Hex this[int row, int col]  // 取代原来的basic字段
        {
            get
            {
                if (IsValid(row, col)) return hexes[row, col];// 如果是合法六角格坐标，返回六角格信息
                return null;// 取六角格时，如果输入坐标超出地图范围，将得到null值
            }
            set
            {
                if (IsValid(row, col)) hexes[row, col] = value;// 如果是合法六角格坐标，则设置六角格信息
            }
        }


        /// <summary>
        /// 从路径path中导入地图文件，包括basic.json、cost.pickle和see.npz
        /// </summary>
        /// <param name="path"></param>
        public Map(string path)
        {
            // 从basic.json文件中导入数据
            using (StreamReader r = new StreamReader(path + @"\basic.json"))// 路径下的basic.json文件
            {
                string json = r.ReadToEnd();// 读取流的当前位置到结尾的所有字符
                Dictionary<string, object> data_dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);// 反序列化为字典类型数据
                hexes = JsonConvert.DeserializeObject<Hex[,]>(data_dict["map_data"].ToString());// 反序列化data_dict中以map_data为键的数据，保存为Hex[,]类型
                if (data_dict.ContainsKey("ele_grade")) elevGrade = JsonConvert.DeserializeObject<int>(data_dict["ele_grade"].ToString());// 反序列化data_dict中以ele_grade为键的数据，保存为int类型
                else elevGrade = 20;  // 默认20m的高差
                if (data_dict.ContainsKey("radius")) width = JsonConvert.DeserializeObject<int>(data_dict["width"].ToString());// 反序列化data_dict中以width为键的数据，保存为int类型
                else width = 200;  // 默认200m的半径
                maxRow = hexes.GetLength(0);// 保存最大行数
                maxCol = hexes.GetLength(1);// 保存最大列数
            }

            // 从cost.json文件中导入数据
            using (StreamReader r = new StreamReader(path + @"\cost.json"))// 路径下的cost.json文件
            {
                string json = r.ReadToEnd();// 读取流的当前位置到结尾的所有字符
                cost = JsonConvert.DeserializeObject<List<List<List<Dictionary<int, float>>>>>(json);// 反序列化
            }

            // 从see.npz文件中导入数据
            var data_test = np.Load_Npz<bool[,,,,]>(path + @"\see.npz");// 路径下的see.npz文件
            see = data_test["data.npy"];
        }

        /// <summary>
        /// 检查坐标pos是否在地图内
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        private bool IsValid(int pos)
        {
            int row = pos / 100;// 取得行
            int col = pos % 100;// 取得列
            if ((row >= 0 && row < maxRow) && (col >= 0 && col < maxCol)) return true;// 行数不超过最大行，列数不超过最大列
            else
            {
                Trace.TraceWarning($"输入坐标[{row}, {col}]超出地图范围");
                return false;
            }
        }

        private bool IsValid(int row, int col)
        {
            if ((row >= 0 && row < maxRow) && (col >= 0 && col < maxCol)) return true;// 行数不超过最大行，列数不超过最大列
            else
            {
                Trace.TraceWarning($"输入坐标[{row}, {col}]超出地图范围");
                return false;
            }
        }

        public Hex[,] GetMapData()// 取得地图六角格数据
        {
            return hexes;
        }

        public int[] GetNeighbors(int pos)// 取得输入六角格的邻格
        {
            if (!IsValid(pos))// 如果是不合规的坐标
            {
                return null;// 返回null值
            }
            int row = pos / 100;// 取得行
            int col = pos % 100;// 取得列
            return hexes[row, col].neighbors;// 返回邻格数据
        }
        public int[] GetNeighbors(int row, int col)// 重载方法 ↑
        {
            if (!IsValid(row, col))
            {
                return null;
            }
            return hexes[row, col].neighbors;
        }

        public Dictionary<int, float> GetHexCosts(int pos, MoveMode moveMode)// 取得到邻域的通行代价，通行代价定义为当前通行模式下最大机动速度/当前速度
        {
            if (!IsValid(pos))// 如果是不合规的坐标
            {
                return null;// 返回null值
            }
            int row = pos / 100;// 取得行
            int col = pos % 100;// 取得列
            return cost[(int)moveMode][row][col];// 返回取得的通行代价
        }

        public Dictionary<int, float> GetHexCosts(int row, int col, MoveMode moveMode)// 重载方法 ↑
        {
            if (!IsValid(row, col))
            {
                return null;
            }
            return cost[(int)moveMode][row][col];
        }

        public bool CanSee(int pos1, int pos2, int mode = 0)// 判断以某种模式的两点之间是否通视
        {
            if (!IsValid(pos1))// 位置1不合规
            {
                return false;  // 出错就不能通视
            }
            if (!IsValid(pos2))// 位置1不合规
            {
                return false;
            }
            if (mode < 0 || mode >= see.shape[0])// 通视模式不合规
            {
                Trace.TraceWarning($"输入了不正确的通视模式: {mode}");
                return false;
            }
            int row1 = pos1 / 100;// 取得位置1的行
            int col1 = pos1 % 100;// 取得位置1的列
            int row2 = pos2 / 100;// 取得位置2的行
            int col2 = pos2 % 100;// 取得位置2的列
            return see[mode][row1, col1, row2, col2];// 返回取得的判断结果
        }
        /// <summary>
        /// 判断算子之间是否通视
        /// </summary>
        /// <returns></returns>
        public bool CanSeeTerrain(OprType type, OprType target_type, int opr_cur_hex, int target_cur_hex, OprSubType target_sub_type, Dictionary<OprSubType, int> opr_observe_distance, MoveStateType target_move_state)
        {
            int mode = GetMode(type, target_type);// 取得通视模式
            if (mode < 0) return false;// 没有的通视模式

            if (!CanSee(opr_cur_hex, target_cur_hex, mode)) return false;// 己方算子和目标算子的所在六角格之间不能通视，则算子之间也不能通视

            int dist = GetDistance(opr_cur_hex, target_cur_hex);// 取得算子之间的距离
            // 坦克对坦克的视野最大是25格
            int observe_dist = opr_observe_distance[target_sub_type];// 取得己方算子对目标类型的算子的最大观察距离

            int row = target_cur_hex / 100;
            int col = target_cur_hex % 100;
            int opr_row = opr_cur_hex / 100;
            int opr_col = opr_cur_hex % 100;

            // 优势地形，视距减半
            if (new List<TerrainType> { TerrainType.Forest, TerrainType.City }.Contains(hexes[row, col].cond))// 目标所在地形为森林或者居民地
            {
                observe_dist = observe_dist / 2;// 视距减半
            }

            // 目标高程优势+目标掩蔽，视距减半 飞机对车辆除外
            if ((hexes[opr_row, opr_col].elev <= hexes[row, col].elev) && target_move_state == MoveStateType.Shelt && (type != OprType.Airplane || target_type != OprType.Vehicle))
            {
                observe_dist = observe_dist / 2;// 视距减半
            }

            if (dist > observe_dist) return false;// 算子之间的距离大于视距，不可通视

            return true;// 否则可以通视
        }
        // 重载方法 ↑
        public bool CanSeeTerrain(int type, int target_type, int opr_cur_hex, int target_cur_hex, int target_sub_type, Dictionary<int, int> opr_observe_distance, int target_move_state)
        {
            int mode = GetMode((OprType)type, (OprType)target_type);
            if (mode < 0) return false;

            if (!CanSee(opr_cur_hex, target_cur_hex, mode)) return false;

            int dist = GetDistance(opr_cur_hex, target_cur_hex);
            // 坦克对坦克的视野最大是25格
            int observe_dist = opr_observe_distance[target_sub_type];

            int row = target_cur_hex / 100;
            int col = target_cur_hex % 100;
            int opr_row = opr_cur_hex / 100;
            int opr_col = opr_cur_hex % 100;

            // 优势地形，视距减半
            if (new List<TerrainType> { TerrainType.Forest, TerrainType.City }.Contains(hexes[row, col].cond))
            {
                observe_dist = observe_dist / 2;
            }

            // 高程优势+掩蔽，视距减半 飞机对车辆除外
            if ((hexes[opr_row, opr_col].elev <= hexes[row, col].elev) && target_move_state == (int)MoveStateType.Shelt && ((OprType)type != OprType.Airplane || (OprType)target_type != OprType.Vehicle))
            {
                observe_dist = observe_dist / 2;
            }

            if (dist > observe_dist) return false;

            return true;
        }

        public int GetDistance(int pos1, int pos2)// 计算算子之间的距离
        {
            if (!IsValid(pos1))
            {
                return -1;  // -1表示不正确的距离
            }
            if (!IsValid(pos2))
            {
                return -1;
            }
            // Convert positions to cube coordinates
            int row1 = pos1 / 100;
            int col1 = pos1 % 100;
            int q1 = col1 - (row1 - (row1 & 1)) / 2;
            int r1 = row1;
            int s1 = -q1 - r1;
            int row2 = pos2 / 100;
            int col2 = pos2 % 100;
            int q2 = col2 - (row2 - (row2 & 1)) / 2;
            int r2 = row2;
            int s2 = -q2 - r2;
            // Calculate Manhattan distance
            return Math.Abs(q1 - q2) + Math.Abs(r1 - r2) + Math.Abs(s1 - s2) / 2;
        }

        /// <summary>
        /// 计算六角格中心坐标，width表示六角格的宽度，即相邻2个六角格中心的距离，一般为200米
        /// 六角格地图首行首列为第0行第0列，六角格尖顶朝上，奇数行（第1、3、5……）右缩
        /// </summary>
        /// <param name="pos">六角格偏移坐标，4位整型</param>
        /// <param name="width">六角格宽度，即相邻2个六角格中心的距离</param>
        /// <returns></returns>
        private Point2D CalcCentralCoordinates(int pos, int width)
        {
            Point2D point = new Point2D();
            int row = pos / 100;
            int col = pos % 100;
            float height = (float)(width / Math.Sqrt(3) * 2);
            if (row % 2 == 0)
            {
                point.X = (float)width * col + width / 2;
            }
            else
            {
                point.X = (float)width * col + width;
            }
            point.Y = (float)height * row + height / 2;
            return point;
        }

        /// <summary>
        /// 计算六角格pos1中心坐标到pos2中心的距离，单位米
        /// </summary>
        /// <param name="pos1">六角格偏移坐标，4位整型</param>
        /// <param name="pos2">六角格偏移坐标，4位整型</param>
        /// <returns></returns>
        public float GetDistanceInMeter(int pos1, int pos2)
        {
            Point2D center1 = new Point2D();
            Point2D center2 = new Point2D();
            float dist;
            if (!IsValid(pos1) || !IsValid(pos2))
            {
                return -1;
            }
            center1 = CalcCentralCoordinates(pos1, Width);
            center2 = CalcCentralCoordinates(pos2, Width);
            dist = (float)Math.Sqrt(Math.Pow(center1.X - center2.X, 2) + Math.Pow(center1.Y - center2.Y, 2));
            return dist;
        }

        /// <summary>
        /// 使用A*算法计算从begin到end的路径
        /// </summary>
        /// <param name="begin"></param>
        /// <param name="end"></param>
        /// <param name="mode"></param>
        /// <returns></returns>
        public List<int> GenMoveRoute(int begin, int end, int mode)
        {
            SortedSet<Tuple<float, float, int>> frontier = new SortedSet<Tuple<float, float, int>>();
            Dictionary<int, float> cost_so_far = new Dictionary<int, float>();
            Dictionary<int, int> came_from = new Dictionary<int, int>();


            if (!IsValid(begin))
            {
                return null;
            }
            if (!IsValid(end))
            {
                return null;
            }
            if (mode < 0 || mode >= cost.Count)
            {
                Trace.TraceWarning($"输入了不正确的通视模式: {mode}");
                return null;
            }
            if (begin == end)
            {
                Trace.TraceWarning($"机动路线计算中输入的起始位置和终止位置相同");
                return null;
            }

            // BFS with priority queue to find one of the fastest move path from `begin` to `end` with given `mode`
            frontier.Add(new Tuple<float, float, int>(0, (float)new Random().NextDouble(), begin));
            cost_so_far[begin] = 0;
            came_from[begin] = -1;  // -1表示没有父节点

            while (frontier.Count > 0)
            {
                Tuple<float, float, int> current = frontier.First();
                frontier.Remove(current);

                //float cost = current.Item2;
                int cur = current.Item3;
                int row = cur / 100;
                int col = cur % 100;

                if (cur == end) break;

                foreach (KeyValuePair<int, float> neigh_n_cost in cost[mode][row][col])
                {
                    int neigh = neigh_n_cost.Key;
                    float edge_cost = neigh_n_cost.Value;
                    float neighCost = cost_so_far[cur] + edge_cost;

                    if (cost_so_far.ContainsKey(neigh) && neighCost < cost_so_far[neigh]) continue;
                    cost_so_far[neigh] = neighCost;
                    came_from[neigh] = cur;
                    int heuristic = this.GetDistance(neigh, end);
                    frontier.Add(new Tuple<float, float, int>(neighCost + heuristic, (float)new Random().NextDouble(), neigh));
                }
            }

            List<int> path = new List<int>();

            if (came_from.ContainsKey(end))
            {
                int cur = end;
                while (cur != begin)
                {
                    path.Append(cur);
                    cur = came_from[cur];
                }
                path.Reverse();
            }

            return path;
        }

        /// <summary>
        /// 取得算子之间的通视模式
        /// </summary>
        /// <param name="opr_type"></param>
        /// <param name="target_type"></param>
        /// <returns></returns>
        public int GetMode(OprType opr_type, OprType target_type)
        {
            if ((opr_type == OprType.Infantry || opr_type == OprType.Vehicle) && (target_type == OprType.Infantry || target_type == OprType.Vehicle))// 己方是地面算子，目标也是地面算子
            {
                return 0;  // 地对地模式
            }
            else if ((opr_type == OprType.Infantry || opr_type == OprType.Vehicle) && target_type == OprType.Airplane)// 己方是地面算子，目标是空中算子
            {
                return 2;  // 地对空模式
            }
            else if ((target_type == OprType.Infantry || target_type == OprType.Vehicle) && opr_type == OprType.Airplane)// 己方是空中算子，目标是地面算子
            {
                return 2;  // 空对地模式
            }
            else if (opr_type == OprType.Airplane && target_type == OprType.Airplane)// 己方是空中算子，目标是空中算子
            {
                return 1;  // 空对空模式
            }
            else
            {
                Trace.TraceWarning($"暂时不能计算此输入的同时模式");
                return -1;  // 无此模式
            }
        }

        /// <summary>
        /// 二维点
        /// </summary>
        private struct Point2D
        {
            public float X;
            public float Y;
        }
    }
}
